/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package jplurk.api;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jplurk.api.util.BaseConvert;
import jplurk.api.util.ResponseData;
import org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.ProxyHost;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
 *
 * @author Jose
 */
public class PlurkApi {

    private static Logger logger = Logger.getLogger(PlurkApi.class);
    
    public final static String BASE_URL = "http://www.plurk.com";
    public final static String LOGIN_URL = BASE_URL + "/Users/login";
    public final static String LOGOUT_URL = BASE_URL + "/Users/logout";
    public final static String GET_PLURKS_URL = BASE_URL + "/TimeLine/getPlurks";
    public final static String GET_UNREAD_PLURKS_URL = BASE_URL + "/TimeLine/getUnreadPlurks";
    public final static String GET_USER_DATA_URL =  BASE_URL + "/Users/getUserData";
    public final static String ADD_PLURK_URL = BASE_URL + "/TimeLine/addPlurk";
    public final static String RESPOND_TO_PLURK_URL = BASE_URL + "/Responses/add";
    public final static String GET_RESPONSES_URL = BASE_URL + "/Responses/get2";
    public final static String DELETE_PLURK_URL = BASE_URL + "/TimeLine/deletePlurk";
    public final static String MUTE_PLURK_URL = BASE_URL + "/TimeLine/setMutePlurk";
    public final static String PERMALINK_BASE = BASE_URL + "/p/";
    public final static String GET_NOTIFICATIONS_URL = BASE_URL + "/Notifications";
    public final static String GET_NOTIFICATIONS_COUNT_URL = BASE_URL + "/Notifications/getCount";
    public final static String GET_FRIENDS_BASE_URL = BASE_URL + "/Friends";
    public final static String GET_FRIENDS_URL = GET_FRIENDS_BASE_URL + "/getFriendsByOffset";
    public final static String GET_FANS_URL = GET_FRIENDS_BASE_URL + "/getFansByOffset";
    public final static String GET_FOLLOWING_URL = GET_FRIENDS_BASE_URL + "/getFollowingByOffset";
    public final static String GET_BLOCKED_USERS_URL = GET_FRIENDS_BASE_URL + "/getBlockedByOffset";
    public final static String GET_CLIQUES_URL = GET_FRIENDS_BASE_URL + "/?page=cliques";
    public final static String SET_FOLLOWING_FLAG_URL = BASE_URL + "/Users/setFollowingTL";
    public final static String REMOVE_FRIEND_URL = BASE_URL + "/Users/removeFriend";
    public final static String REMOVE_BLOCK_URL = BASE_URL + "/Users/removeBlock";
    public final static String ALLOW_FRIEND_URL = BASE_URL + "/Notifications/allow";
    public final static String MAKE_FAN_URL = BASE_URL + "/Notifications/allowDontFollow";
    public final static String DENY_FRIEND_URL = BASE_URL + "/Notifications/deny";
    public final static String REQUEST_FRIEND_URL = BASE_URL + "/Notifications/addNotification";
    public final static String CANCEL_REQUEST_FRIEND_URL = BASE_URL + "/Notifications/removeNotification";
    public final static String ADD_CLIQUE_URL = BASE_URL + "/Cliques/add";
    public final static String DELETE_CLIQUE_URL = BASE_URL + "/Cliques/deleteClique";
    public final static String CHANGE_NAME_CLIQUE_URL = BASE_URL + "/Cliques/changeName";
    public final static String ADD_FRIEND_TO_CLIQUE_URL = BASE_URL + "/Cliques/update";
    public static String BLOCK_USER_URL = BASE_URL + "/Friends/blockUser?block_uid=:user_id";
    public static String GET_USER_URL = BASE_URL + "/user/:username";
    
    private HttpClient client;
    private String httpProxyHost;
    private Integer httpProxyPort;
    private String httpProxyUser;
    private String httpProxyPassword;
    private Cookie plurkCookie;
    private String username;
    private String password;
    private String userId;
    private Boolean loggedIn = false;
    private Integer timeout = 30000;
    
    private JSONObject sessionUser;
    private JSONObject sessionUserSettings;
    private JSONObject sessionUserData;
    private JSONArray plurkArray = null;

    /**
     * Create a PlurkApi instance. <br/>
     * Initializes the HttpClient object that will be used to invoke the Plurk
     * URLs and sets the timeout.<br/>
     * The constructor will also configure the HTTP Proxy options if the
     * system settings are set.
     */
    public PlurkApi() {
        this.client = new HttpClient();
        this.client.getHttpConnectionManager().getParams().setConnectionTimeout(this.timeout);
        
        // See whether we must configure an HTTP proxy or not
        configureHttpProxy();
        // See whether we must configure HTTP proxy authentication or not
        configureHttpProxyCredentials();
    }

    /**
     * Creates a PlurkApi instance for a given user.<br/>
     * @param username of the user that is represented by this PlurkApi instance.
     * @param password of the user to access the Plurk service.
     */
    public PlurkApi(String username, String password) {
        this();
        this.username = username;
        this.password = password;
    }

    /**
     * Logs in the user to the Plurk service. <br/>
     * This method uses the instance variables 'username' and 'password' to log the 
     * user in.
     * @return true if the user is successfully logged in; false, otherwise.
     */
    public boolean login() {

        // If the user is already logged in, then we simply return true
        if (this.loggedIn) {
            logger.warn("Trying to log in a user that is already logged in!");
            return this.loggedIn;
        }
        
        // Create a method instance.
        PostMethod method = new PostMethod(LOGIN_URL);

        // Set the POST parameters
        NameValuePair[] data = {
            new NameValuePair("nick_name", this.username),
            new NameValuePair("password", this.password)
        };
        
        method.setRequestBody(data);

        try {
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
                                                
            if (resp.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) {
                logger.error("Login failed: " + method.getStatusLine());
                this.loggedIn = false;
            } 
            else {              
                this.plurkCookie = resp.getCookies()[0];

                // The user is now logged in
                this.loggedIn = true;
            }

        } catch (RequestException e) {
            e.printStackTrace();
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return this.loggedIn;
    }

    /**
     * Logs the user out of the Plurk service. <br/>
     * This method uses the instance variables 'username' and 'password' to log the 
     * user in.
     * @return True if the user is logged out successfully; false, otherwise.
     * @throws jplurk.api.NotLoggedInException when the user is not previously logged in
     */
    public boolean logout() throws NotLoggedInException {
        
        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        PostMethod method = new PostMethod(LOGOUT_URL);
        
        try {
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
            if (resp.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) {
                logger.error("Logout failed: " + method.getStatusLine());
            }
            else {
                // The user is now logged out
                this.loggedIn = false;
            }

        } catch (RequestException e) {
            e.printStackTrace();
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return !this.loggedIn;
    }
    
    public Boolean querySessionUser() throws NotLoggedInException {

        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        GetMethod method = new GetMethod(GET_USER_URL.replace(":username", this.username));

        try {
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
        
            // Retrieve the GLOBAL JSON String
            Pattern userIdPattern = Pattern.compile("var GLOBAL = \\{.*\"uid\": ([\\d]+),.*\\}");
            Matcher userIdMatcher = userIdPattern.matcher(resp.getBody());
            userIdMatcher.find();
            
            // Get the Session User ID
            this.userId = userIdMatcher.group(1);         
            
            // Create the session user JSONObject
            this.sessionUser = new JSONObject(userIdMatcher.group().replace("var GLOBAL = ", "" )).getJSONObject("session_user");

            // Retrieve the SETTINGS JSON String
            Pattern settingsPattern = Pattern.compile("var SETTINGS = \\{.*\\}");         
            Matcher settingsMatcher = settingsPattern.matcher(resp.getBody());
            settingsMatcher.find();

            // Create the session settings user JSONObject
            this.sessionUserSettings = new JSONObject(settingsMatcher.group().replace("var SETTINGS = ", "" ));
                        
            if (logger.isTraceEnabled()) {
                logger.trace("User ID: " + this.userId);
                logger.trace("Session User JSON: " + this.sessionUser.toString());
                logger.trace("Session User Settings JSON: " + this.sessionUserSettings.toString());
            }

        } catch (RequestException e) {
            e.printStackTrace();
            return false;
        } catch (JSONException e) {
            e.printStackTrace();
            return false;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return true;
    }
    
    public JSONObject queryUser(String username) {

        // Create a method instance.
        GetMethod method = new GetMethod(GET_USER_URL.replace(":username", username));

        JSONObject pageUser = null;
        
        try {
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
            // Retrieve the GLOBAL JSON String
            Pattern pageIdPattern = Pattern.compile("var GLOBAL = \\{.*\"uid\": ([\\d]+),.*\\}");           
            Matcher pageIdMatcher = pageIdPattern.matcher(new String(resp.getBody()));
            pageIdMatcher.find();
           
            // Create the page user JSONObject
            pageUser = new JSONObject(pageIdMatcher.group().replace("var GLOBAL = ", "" )).getJSONObject("page_user");

            if (logger.isTraceEnabled()) {
                logger.trace("Page User JSON: " + pageUser.toString());
            }
            
        } catch (RequestException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return pageUser;
    }    
    
    public Boolean queryUserData() throws NotLoggedInException {

        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        PostMethod method = new PostMethod(GET_USER_DATA_URL);

        NameValuePair[] data = {
            new NameValuePair("page_uid", this.userId)
        };
        method.setRequestBody(data);

        try {
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
            // Create the session user data JSONObject
            this.sessionUserData = new JSONObject(new String(resp.getBody()));
            
            if (logger.isTraceEnabled()) {
                logger.trace("Session User Data JSON: " + this.sessionUserData.toString());
            }
            
        } catch (RequestException e) {
            e.printStackTrace();
            return false;
        } catch (JSONException e) {
            e.printStackTrace();
            return false;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return true;
    }
    
    public JSONArray getPlurks(String userId) {
        
        // Create a method instance.
        PostMethod method = new PostMethod(GET_PLURKS_URL);

        NameValuePair[] data = {
            new NameValuePair("user_id", userId)
        };
        method.setRequestBody(data);
        
        JSONArray plurks = null;
                
        try {
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
            // Create the Pluark JSONArray
            plurks = new JSONArray(new String(resp.getBody()));

            if (logger.isTraceEnabled()) {
                logger.trace("Plurk JSON Array: " + plurks.toString());
                for (int i = 0; i < plurks.length(); i++) {
                    logger.trace("Plurk " + i + " : " + plurks.get(i));
                }
            }

        } catch (RequestException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return plurks;
    }
    
    // TODO: getPlurks after the first 15
    public JSONArray getPlurks() throws NotLoggedInException {

        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        this.plurkArray = this.getPlurks(this.userId);
        
        return this.plurkArray;
    }
    
    public JSONArray getUnreadPlurks() throws NotLoggedInException {
        
        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        PostMethod method = new PostMethod(GET_UNREAD_PLURKS_URL);

        NameValuePair[] data = {
            new NameValuePair("user_id", this.userId)
        };
        method.setRequestBody(data);
              
        try {
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);

            // Create the Pluark JSONArray
            this.plurkArray = new JSONArray(new String(resp.getBody()));
                        
            if (logger.isTraceEnabled()) {
                logger.trace("Plurk JSON Array: " + plurkArray.toString());
                for (int i = 0; i < plurkArray.length(); i++) {
                    logger.trace("Plurk " + i + " : " + plurkArray.get(i));
                }
            }

        } catch (RequestException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return this.plurkArray;
    }
    
    public JSONObject addPlurk(String lang, String qualifier, String content, Boolean allow_comments) throws NotLoggedInException {
  
        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Default values
        this.setPlurkDefaults(lang, qualifier, content, allow_comments);
        
        // Format the posted date/time
        DateFormat postDateFormatter = new SimpleDateFormat("yyyy-M-d'T'HH:mm:ss");
        String posted = postDateFormatter.format((Calendar.getInstance(TimeZone.getTimeZone("GMT"), new Locale("en"))).getTime());
        
        // Create a method instance.
        PostMethod method = new PostMethod(ADD_PLURK_URL);

        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("lang", lang),
            new NameValuePair("qualifier", qualifier),
            new NameValuePair("content", content),
            new NameValuePair("no_comments", (allow_comments ? 0 : 1) + ""),
            new NameValuePair("posted", posted),
            new NameValuePair("uid", this.userId)
        };
        method.setRequestBody(data);

        // Prepare a JSON object
        JSONObject addedPlurk;
        
        try {               
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);

            // Create the Plurk JSONObject
            addedPlurk = new JSONObject(new String(resp.getBody()));

            if (logger.isTraceEnabled()) {
                logger.trace("Add Plurk: " + posted + " "
                                       + lang + " "
                                       + qualifier + " "
                                       + content + " "
                                       + allow_comments + " "
                                       + this.userId);
                logger.trace("Added Plurk JSON: " + addedPlurk.toString());
            }

        } catch (RequestException e) {
            e.printStackTrace();
            return null;
        } catch (JSONException e) {
            e.printStackTrace();
            return null;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return addedPlurk;
    }
    
    public JSONObject respondToPlurk(Integer plurkId, Integer ownerId, String lang, String qualifier, String content) throws NotLoggedInException {
  
        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Default values
        this.setPlurkDefaults(lang, qualifier, content, false);
        
        // Format the posted date/time
        DateFormat postDateFormatter = new SimpleDateFormat("yyyy-M-d'T'HH:mm:ss");
        String posted = postDateFormatter.format((Calendar.getInstance(TimeZone.getTimeZone("GMT"), new Locale("en"))).getTime());
        
        // Create a method instance.
        PostMethod method = new PostMethod(RESPOND_TO_PLURK_URL);

        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("plurk_id", plurkId.toString()),
            new NameValuePair("p_uid", ownerId.toString()),
            new NameValuePair("lang", lang),
            new NameValuePair("qualifier", qualifier),
            new NameValuePair("content", content),
            new NameValuePair("posted", posted),
            new NameValuePair("uid", this.userId)
        };
        method.setRequestBody(data);
        
        // Prepare a JSON object for the response
        JSONObject respondedPlurk;
                
        try {
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
            // Create the Plurk JSONObject
            respondedPlurk = new JSONObject(new String(resp.getBody()));         
            
            if (logger.isTraceEnabled()) {
                logger.trace("Respond to Plurk: " + posted + " "
                                              + plurkId + " "
                                              + lang + " "
                                              + qualifier + " "
                                              + content + " "
                                              + this.userId);
                logger.trace("Responded to Plurk JSON: " + respondedPlurk.toString());
            }

        } catch (RequestException e) {
            e.printStackTrace();
            return null;
        } catch (JSONException e) {
            e.printStackTrace();
            return null;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return respondedPlurk;
    }
    
    public JSONObject getResponses(Integer plurkId) throws NotLoggedInException {
        
        // Create a method instance.
        PostMethod method = new PostMethod(GET_RESPONSES_URL);

        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("plurk_id", plurkId.toString())
        };
        method.setRequestBody(data);
        
        // Prepare the JSON object that will contain the reponses
        JSONObject plurkResponses;
        
        try {
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            // Create the Plurk JSONObject
            plurkResponses = new JSONObject(new String(resp.getBody()));
                        
            if (logger.isTraceEnabled()) {
                logger.trace("Responses to Plurk: " + plurkId);
                logger.trace("Plurk responses JSON: " + plurkResponses.toString());
            }

        } catch (RequestException e) {           
            e.printStackTrace();
            return null;        
        } catch (JSONException e) {
            e.printStackTrace();
            return null;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return plurkResponses;
    }
    
    public Boolean deletePlurk(Integer plurkId) throws NotLoggedInException {
        
        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        PostMethod method = new PostMethod(DELETE_PLURK_URL);

        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("plurk_id", plurkId.toString())
        };
        method.setRequestBody(data);

        try {
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
            if (logger.isTraceEnabled()) {
                logger.trace("Delete Plurk: " + plurkId);
            }   

        } catch (RequestException e) {
            e.printStackTrace();
            return false;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return true;
    }
    
    public JSONArray getFriends(Integer offset) throws NotLoggedInException {

        // Default offset is 0
        if (offset == null) {
            offset = 0;
        }
        
        // Create a method instance.
        PostMethod method = new PostMethod(GET_FRIENDS_URL);
        
        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("user_id", this.userId),
            new NameValuePair("offset", offset.toString())
        };
        method.setRequestBody(data);
        
        JSONArray friends = null;
        try {
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
            // Create the Friends JSONArray
            friends = new JSONArray(new String(resp.getBody()));
            
            if (logger.isTraceEnabled()) {
                logger.trace("Friends JSON Array: " + friends.toString());
                for (int i = 0; i < friends.length(); i++) {
                    logger.trace("Friend " + i + " : " + friends.get(i));
                }
            }

        } catch (RequestException e) {
            e.printStackTrace();       
        } catch (JSONException e) {
            e.printStackTrace();
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return friends;
    }
    
    public JSONArray getFans(Integer offset) throws NotLoggedInException {
        
        // Default offset is 0
        if (offset == null) {
            offset = 0;
        }
        
        // Create a method instance.
        PostMethod method = new PostMethod(GET_FANS_URL);
        
        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("user_id", this.userId),
            new NameValuePair("offset", offset.toString())
        };
        method.setRequestBody(data);
        
        JSONArray fans = null;
        try {
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
            // Create the Friends JSONArray
            fans = new JSONArray(new String(resp.getBody()));
                       
            if (logger.isTraceEnabled()) {            
                logger.trace("Fans JSON Array: " + fans.toString());                 
                for (int i = 0; i < fans.length(); i++) {
                    logger.trace("Friend " + i + " : " + fans.get(i));
                }
            }

        } catch (RequestException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return fans;
    }
    
    public JSONArray getFollowing(Integer offset) throws NotLoggedInException {

        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Default offset is 0
        if (offset == null) {
            offset = 0;
        }
        
        // Create a method instance.
        PostMethod method = new PostMethod(GET_FOLLOWING_URL);
        
        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("user_id", this.userId),
            new NameValuePair("offset", offset.toString())
        };
        method.setRequestBody(data);
        
        JSONArray following = null;
        try {
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
            // Create the Friends JSONArray
            following = new JSONArray(new String(resp.getBody()));
                       
            if (logger.isTraceEnabled()) {
                logger.trace("Following JSON Array: " + following.toString());            
                for (int i = 0; i < following.length(); i++) {
                    logger.trace("Friend " + i + " : " + following.get(i));
                }
            }

        } catch (RequestException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return following;
    }
    
    public JSONArray getBlockedUsers(Integer offset) throws NotLoggedInException {

        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Default offset is 0
        if (offset == null) {
            offset = 0;
        }
        
        // Create a method instance.
        PostMethod method = new PostMethod(GET_BLOCKED_USERS_URL);
        
        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("user_id", this.userId),
            new NameValuePair("offset", offset.toString())
        };
        method.setRequestBody(data);
        
        JSONArray blocked = null;
        try {
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
            // Create the blocked JSONArray
            blocked = new JSONArray(new String(resp.getBody()));
            
            if (logger.isTraceEnabled()) {                
                logger.trace("Blocked JSON Array: " + blocked.toString());            
                for (int i = 0; i < blocked.length(); i++) {
                    logger.trace("Friend " + i + " : " + blocked.get(i));
                }
            }

        } catch (RequestException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return blocked;
    }
    
    public Boolean setFollowingFlag(Integer friendId, Boolean follow) throws NotLoggedInException {

        Boolean result = true;
        
        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        PostMethod method = new PostMethod(SET_FOLLOWING_FLAG_URL);
        
        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("friend_id", friendId.toString()),
            new NameValuePair("value", (follow ? "1" : "0"))
        };
        method.setRequestBody(data);

        try {
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);            

        } catch (RequestException e) {
            e.printStackTrace();
            result = false;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return result;
    }
    
    public Boolean removeFriend(Integer friendId) throws NotLoggedInException {

        Boolean result = true;
        
        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        PostMethod method = new PostMethod(REMOVE_FRIEND_URL);
        
        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("friend_id", friendId.toString())
        };
        method.setRequestBody(data);

        try {            
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
        } catch (RequestException e) {
            e.printStackTrace();
            result = false;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return result;
    }
    
    public Boolean removeFanAndBlock(Integer friendId) throws NotLoggedInException {
        
        return removeFriend(friendId);
    }
    
    public Boolean removeFollowing(Integer friendId) throws NotLoggedInException {
        
        return removeFriend(friendId);
    }
    
    public Boolean removeBlock(Integer friendId) throws NotLoggedInException {

        Boolean result = true;
        
        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        PostMethod method = new PostMethod(REMOVE_BLOCK_URL);
        
        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("friend_id", friendId.toString())
        };
        method.setRequestBody(data);
        
        try {           
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
        } catch (RequestException e) {
            e.printStackTrace();
            result = false;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return result;
    }
    
    public Boolean allowFriend(Integer friendId) throws NotLoggedInException {

        Boolean result = true;
        
        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        PostMethod method = new PostMethod(ALLOW_FRIEND_URL);
        
        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("friend_id", friendId.toString())
        };
        method.setRequestBody(data);
        
        try {          
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
        } catch (RequestException e) {
            logger.error("Fatal protocol violation: " + e.getMessage());
            e.printStackTrace();
            result = false;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return result;
    }
    
    public Boolean denyFriend(Integer friendId) throws NotLoggedInException {

        Boolean result = true;
        
        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        PostMethod method = new PostMethod(DENY_FRIEND_URL);
        
        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("friend_id", friendId.toString())
        };
        method.setRequestBody(data);
        
        try {          
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
        } catch (RequestException e) {
            logger.error("Fatal protocol violation: " + e.getMessage());
            e.printStackTrace();
            result = false;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return result;
    }
    
    public Boolean makeFan(Integer friendId) throws NotLoggedInException {

        Boolean result = true;
        
        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        PostMethod method = new PostMethod(MAKE_FAN_URL);
        
        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("friend_id", friendId.toString())
        };
        method.setRequestBody(data);

        try {          
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
        } catch (RequestException e) {
            logger.error("Fatal protocol violation: " + e.getMessage());
            e.printStackTrace();
            result = false;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return result;
    }
    
    public Boolean requestFriend(Integer friendId) throws NotLoggedInException {

        Boolean result = true;
        
        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        PostMethod method = new PostMethod(REQUEST_FRIEND_URL);
        
        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("friend_id", friendId.toString())
        };
        method.setRequestBody(data);
        

        try {          
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
        } catch (RequestException e) {
            logger.error("Fatal protocol violation: " + e.getMessage());
            e.printStackTrace();
            result = false;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return result;
    }
    
    public Boolean cancelFriendRequest(Integer friendId) throws NotLoggedInException {

        Boolean result = true;
        
        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        PostMethod method = new PostMethod(CANCEL_REQUEST_FRIEND_URL);
        
        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("friend_id", friendId.toString())
        };
        method.setRequestBody(data);

        try {          
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
        } catch (RequestException e) {
            logger.error("Fatal protocol violation: " + e.getMessage());
            e.printStackTrace();
            result = false;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return result;
    }
    
    public JSONObject blockUser(Integer user_id) {

        // Create a method instance.
        GetMethod method = new GetMethod(BLOCK_USER_URL.replace(":user_id", user_id.toString()));

        JSONObject pageUser = null;
        
        try {
            // Execute the method.
            int statusCode = client.executeMethod(method);

            if (statusCode != HttpStatus.SC_OK) {
                logger.error("Request failed: " + method.getStatusLine());
            }

            // Read the response body.
            byte[] responseBody = method.getResponseBody();

            // Deal with the response.
            logger.trace(new String(responseBody));

            // Retrieve the GLOBAL JSON String
            //Pattern pageIdPattern = Pattern.compile("var GLOBAL = \\{.*\"uid\": ([\\d]+),.*\\}");
            //logger.debug("Regex pattern:" + pageIdPattern.pattern());
            
            //Matcher pageIdMatcher = pageIdPattern.matcher(new String(responseBody));
            //pageIdMatcher.find();
           
            // Create the page user JSONObject
            //pageUser = new JSONObject(pageIdMatcher.group().replace("var GLOBAL = ", "" )).getJSONObject("page_user");
            //logger.debug("Page User JSON: " + pageUser.toString());
            
            if (logger.isDebugEnabled()) {
                this.showCookies();
            }
            
        } catch (HttpException e) {
            logger.error("Fatal protocol violation: " + e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
            logger.error("Fatal transport error: " + e.getMessage());
            e.printStackTrace();
        //} catch (JSONException e) {
       //     logger.error("JSON Exception: " + e.getMessage());
       //     e.printStackTrace();
       // } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return pageUser;
    }
    
    public Vector<JSONObject> getCliques() throws NotLoggedInException {

        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        GetMethod method = new GetMethod(GET_CLIQUES_URL);
        
        Vector<JSONObject> cliques = new Vector<JSONObject>();
        try {          
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
            // Retrieve the GLOBAL JSON String
            Pattern cliquePattern = Pattern.compile("Cliques.renderClique\\( \\{.*\\}");
            Matcher cliqueMatcher = cliquePattern.matcher(new String(resp.getBody()));
            
            JSONObject clique = null;
            while (cliqueMatcher.find()) {           
                // Create the cliques JSONObject
                clique = new JSONObject(cliqueMatcher.group().replace("Cliques.renderClique( ", "" ));
                if (clique != null) {
                    cliques.add(clique);
                }
            }
            
            if (logger.isTraceEnabled()) {
                Integer count = 1;
                for (JSONObject jo : cliques) {
                    logger.trace("Clique " + count + ": " + jo.toString());
                    count++;
                }
            }

        } catch (RequestException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            logger.error("JSON Exception: " + e.getMessage());
            e.printStackTrace();
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return cliques;
    }
    
    public Boolean addClique(String cliqueName) throws NotLoggedInException {

        Boolean result = true;
        
        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        PostMethod method = new PostMethod(ADD_CLIQUE_URL);
        
        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("name", cliqueName)
        };
        method.setRequestBody(data);

        try {         
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
        } catch (RequestException e) {
            e.printStackTrace();
            result = false;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return result;
    }
    
    public Boolean deleteClique(String cliqueName) throws NotLoggedInException {

        Boolean result = true;
        
        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        PostMethod method = new PostMethod(DELETE_CLIQUE_URL);
        
        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("name", cliqueName)
        };
        method.setRequestBody(data);
        
        try {         
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
        } catch (RequestException e) {
            e.printStackTrace();
            result = false;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return result;
    }
    
    public Boolean changeCliqueName(String oldName, String newName) throws NotLoggedInException {

        Boolean result = true;
        
        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        PostMethod method = new PostMethod(CHANGE_NAME_CLIQUE_URL);
        
        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("old_name", oldName),
            new NameValuePair("new_name", newName)
        };
        method.setRequestBody(data);
        
        try {         
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
        } catch (RequestException e) {
            e.printStackTrace();
            result = false;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return result;
    }
    
    public Boolean addFriendToClique(String cliqueName, Integer friendId) throws NotLoggedInException {

        Boolean result = true;
        
        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        PostMethod method = new PostMethod(ADD_FRIEND_TO_CLIQUE_URL);
        
        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("name", cliqueName),
            new NameValuePair("ids", friendId.toString())
        };
        method.setRequestBody(data);
        
        try {         
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
        } catch (RequestException e) {
            e.printStackTrace();
            result = false;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return result;
    }
    
    public Boolean setMutePlurk(Integer plurkId, Boolean mute) throws NotLoggedInException {
        
        Boolean result = true;
         
        // Begin by making sure that the user is logged in at all
        this.checkLoggedIn();
        
        // Create a method instance.
        PostMethod method = new PostMethod(MUTE_PLURK_URL);

        // Add the parameters to the POST request
        NameValuePair[] data = {
            new NameValuePair("plurk_id", plurkId.toString()),
            new NameValuePair("value", (mute ? "2" : "0"))
        };
        method.setRequestBody(data);              
        
        try {         
            // Execute the request
            ResponseData resp = this.sendHttpRequest(method, this.loggedIn);
            
            if (logger.isTraceEnabled()) {
            logger.trace("Mute Plurk: " + plurkId + " mute? " + mute);
            }
            
        } catch (RequestException e) {
            e.printStackTrace();
            result = false;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        return result;
    }
    
    public Long permalinkToPlurkId(String permalink) {
        
        String base36number = permalink.replace(PERMALINK_BASE, "");
        
        return BaseConvert.toDecimal(base36number, 36);
    }
    
    public String plurkIdToPermalink(Long plurkId) {
        
        return PERMALINK_BASE + BaseConvert.fromDecimal(plurkId, 36).toLowerCase();
    }
    
    public void queryNotifications() {
        
        // Create a method instance.
        GetMethod method = new GetMethod(GET_NOTIFICATIONS_URL);

        // Provide custom retry handler is necessary
        method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
                new DefaultHttpMethodRetryHandler(3, false));

        // Set the request cookie that identifies the user
        this.setRequestCookie();
        
        try {
            // Execute the method.
            int statusCode = client.executeMethod(method);

            if (statusCode != HttpStatus.SC_OK) {
                logger.error("Request failed: " + method.getStatusLine());
            }

            // Read the response body.
            byte[] responseBody = method.getResponseBody();

            // Deal with the response.
            logger.trace(new String(responseBody));

//            // Retrieve the GLOBAL JSON String
//            Pattern pageIdPattern = Pattern.compile("var GLOBAL = \\{.*\"uid\": ([\\d]+),.*\\}");
//            logger.debug("Regex pattern:" + pageIdPattern.pattern());
//            
//            Matcher pageIdMatcher = pageIdPattern.matcher(new String(responseBody));
//            pageIdMatcher.find();
//           
//            // Create the page user JSONObject
//            pageUser = new JSONObject(pageIdMatcher.group().replace("var GLOBAL = ", "" )).getJSONObject("page_user");
//            logger.debug("Page User JSON: " + pageUser.toString());
            
            if (logger.isDebugEnabled()) {
                this.showCookies();
            }
            
        } catch (HttpException e) {
            logger.error("Fatal protocol violation: " + e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
            logger.error("Fatal transport error: " + e.getMessage());
            e.printStackTrace();
//        } catch (JSONException e) {
//            logger.error("JSON Exception: " + e.getMessage());
//            e.printStackTrace();
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
    }
    
    public void queryNotificationsCount() {
        
        // Create a method instance.
        GetMethod method = new GetMethod(GET_NOTIFICATIONS_COUNT_URL);

        // Provide custom retry handler is necessary
        method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
                new DefaultHttpMethodRetryHandler(3, false));

        // Set the request cookie that identifies the user
        this.setRequestCookie();
        
        try {
            // Execute the method.
            int statusCode = client.executeMethod(method);

            if (statusCode != HttpStatus.SC_OK) {
                logger.error("Request failed: " + method.getStatusLine());
            }

            // Read the response body.
            byte[] responseBody = method.getResponseBody();

            // Deal with the response.
            logger.trace(new String(responseBody));
            
            if (logger.isDebugEnabled()) {
                this.showCookies();
            }
            
        } catch (HttpException e) {
            logger.error("Fatal protocol violation: " + e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
            logger.error("Fatal transport error: " + e.getMessage());
            e.printStackTrace();
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
    }
        
    public Boolean isLoggedIn() {
        return loggedIn;
    }

    public String getPassword() {
        return password;
    }

    public Cookie getPlurkCookie() {
        return plurkCookie;
    }

    public String getUserId() {
        return userId;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Integer getTimeout() {
        return timeout;
    }

    public void setTimeout(Integer timeout) {
        this.timeout = timeout;
    }

    private void configureHttpProxy() throws NumberFormatException {

        this.httpProxyHost = System.getProperty("http.proxyHost", null);
        this.httpProxyPort = (System.getProperty("http.proxyPort") == null ? null : new Integer(System.getProperty("http.proxyPort", null)));
        if (this.httpProxyHost != null && this.httpProxyPort != null) {
            logger.info("Using proxy configuration...");
            logger.info("\t>Proxy host: " + this.httpProxyHost);
            logger.info("\t>Proxy port: " + this.httpProxyPort);
            this.client.getHostConfiguration().setProxyHost(new ProxyHost(this.httpProxyHost, this.httpProxyPort));
        }
    }

    private void configureHttpProxyCredentials() {

        if (this.httpProxyHost != null && this.httpProxyPort != null) {
            this.httpProxyUser = System.getProperty("http.proxyUser", null);
            this.httpProxyPassword = System.getProperty("http.proxyPassword", null);

            if (this.httpProxyUser != null && this.httpProxyPassword != null) {
                logger.info("Using proxy authentication...");
                logger.info("\t>Proxy user: " + this.httpProxyUser);
                logger.info("\t>Proxy port: " + this.httpProxyPassword.replaceAll(".", "*"));
                this.client.getState().setProxyCredentials(AuthScope.ANY, new UsernamePasswordCredentials(this.httpProxyUser, this.httpProxyPassword));
            }
        }
    }
    
    private void showCookies() {
        Cookie[] cookies = client.getState().getCookies();
        // Display the cookies
        logger.debug("Present cookies: ");
        for (int i = 0; i < cookies.length; i++) {
            logger.debug(" - " + cookies[i].toExternalForm());
        }
    }
    
    private String printCookies() {
        String cookieStr = "Cookies: ";
        Cookie[] cookies = client.getState().getCookies();
        for (int i = 0; i < cookies.length; i++) {
            cookieStr += (" > " + cookies[i].toExternalForm());
        }
        
        return cookieStr;
    }
    
    private void setRequestCookie() {
        // Get state object
        HttpState state = client.getState();
        
        if (state == null) {
            state = new HttpState();
        }
        // Initial set of cookies can be retrieved from persistent storage and 
        // re-created, using a persistence mechanism of choice,
        // and then added to your HTTP state instance
        state.addCookie(this.plurkCookie);
        client.setState(state);
    }
    
    private void checkLoggedIn() throws NotLoggedInException {
        
        if (!this.loggedIn) {
            throw new NotLoggedInException();
        }
    } 
    
    private void setPlurkDefaults(String lang, String qualifier, String content, Boolean allowComments) {
        
        if (lang == null) {
            lang = "en";
        }
        if (qualifier == null) {
            qualifier = "says";
        }
        if (content == null) {
            content = "";
        }
        if (allowComments == null) {
            allowComments = true;
        }
    }
    
    private ResponseData sendHttpRequest(HttpMethod method, Boolean setCookie) throws RequestException {
        
        String responseBody = null;
        Integer statusCode = null;
        Cookie[] cookies = null;
        
        // Provide custom retry handler is necessary
        method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
                new DefaultHttpMethodRetryHandler(3, false));

        // Set the request cookie that identifies the user
        if (setCookie) {
            this.setRequestCookie();
        }
        
        try {
            // Execute the method.
            statusCode = client.executeMethod(method);

            if (statusCode != HttpStatus.SC_OK) {
                throw new RequestException("Request failed: " + method.getStatusLine());
            }

            // Get the response cookies
            cookies = client.getState().getCookies();
             
            // Read the response body.
            responseBody = method.getResponseBody().toString();
                       
            if (logger.isTraceEnabled()) {
                logger.trace("HTTP response: " + new String(responseBody));
                logger.trace("Cookies: " + this.printCookies());
            }

        } catch (HttpException hex) {
            throw new RequestException("Fatal protocol violation: " + hex.getMessage());
        } catch (IOException ioex) {
            throw new RequestException("Fatal transport error: " + ioex.getMessage());
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
        
        // The HTTP response data
        ResponseData resp = new ResponseData(responseBody, statusCode, cookies);
        
        return resp;
    }
    
    public static void main(String[] args) {
        try {
        
        PlurkApi p = new PlurkApi("chemaaa", "mey181096");
        logger.warn("Login");
        p.login();
        
        logger.warn("get /user/username");
        p.querySessionUser();
        //p.queryNotifications();
        logger.warn("getUserData");
        p.queryUserData();
        p.logout();
        logger.warn("get /user/makotogim");
        //.getPlurks(p.queryUser("makotogim").getInt("uid"));
//logger.warn("getPlurks");
//p.getPlurks();
//logger.warn("Respond to Plurk");
//p.respondToPlurk(3141690, 2802345, null, "is", "trying out the API again. Respond functionality.");
//        logger.warn("getUnreadPlurks");
//        p.getUnreadPlurks();
//        logger.warn("Logout");
//        p.logout();
        //p.addPlurk(null, "is", "performing another test... sorry about the SPAM.", true);

//logger.warn("delete Plurk");
//p.deletePlurk(3214333);
//        logger.warn("getPlurks");
//p.getPlurks();
//logger.info(p.plurkIdToPermalink(new Long(3255428)));
//logger.warn("Get responses");
//p.getResponses(3214923);
            //String s = "new Date(\"Thu, 06 Mar 1980 00:01:00 GMT\")";
            //boolean t = Pattern.matches("new Date(\"[Mon|Tue|Wed|Thu|Fri|Sat|Sun], \\d{2} [Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec] \\d{4} \\d{2}:\\d{2}:\\d{2} GMT\")", s);
            //String s = "new Date(\"Thu, 06 Mar 1980 00:01:00 GMT\")";
            ////System.out.println(t);
//            Date d = new Date();
//            SimpleDateFormat p = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", new Locale("en"));
//            p.setTimeZone(new SimpleTimeZone(0,"GMT"));
//            System.out.println(p.format(d));
//            System.out.println(p.parse("Wed, 05 Mar 1980 00:01:00 GMT"));
//        } catch (ParseException ex) {
//            Logger.getLogger(PlurkApi.class.getName()).log(Level.SEVERE, null, ex);
//        }
        //logger.warn("getFriends");

            //p.getFriends(null);
       // logger.warn("getCliques");
       //     p.getCliques();
        } catch (Exception e) {e.printStackTrace();}
    }
}
